 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>jester.js</title>
    <script language="JavaScript" type="text/javascript" src="jsunit/app/jsUnitCore.js"></script>
    <script language="JavaScript" type="text/javascript" src="../prototype.js"></script>
    <script language="JavaScript" type="text/javascript" src="../jester.js"></script>
    <script language="JavaScript" type="text/javascript" src="../firebug/firebug.js"></script>
  </head>

  <body>
    <script language="JavaScript" type="text/javascript">

function setUp () {
  default_prefix = function() {return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":" + window.location.port : "");}

  ericXML = '<?xml version="1.0" encoding="UTF-8"?><user><created-at type="datetime">2007-04-08T15:12:06-04:00</created-at><id type="integer">1</id><first-name>eric</first-name><admin type="boolean">true</admin></user>'
  camelCaseXML = '<?xml version="1.0" encoding="UTF-8"?><cool-thing><created-at type="datetime">2007-04-08T15:12:06-04:00</created-at><id type="integer">1</id><first-name>eric</first-name><admin type="boolean">true</admin></cool-thing>'
  ericJSON = '{attributes: {id: "1", first_name: "eric", admin: "1", created_at: "2007-04-08 15:12:06"}}'
  johnJSON = '{attributes: {id: "2", first_name: "john", admin: "0", created_at: "2007-04-09 15:12:06"}}'
  postJSON = '{attributes: {id: "1", title: "the title", body: "the body"}}'
  usersJSON = "[" + ericJSON + "," + johnJSON + "]"

  postDetails1 = { 'id' : { '@type' : 'integer', '#text' : '1' }, 'title' : 'the title', 'body' : 'the body' }
  postDetails2 = { 'id' : { '@type' : 'integer', '#text' : '2' }, 'title' : 'another title', 'body' : 'another body' }
  postDetails3 = { 'id' : { '@type' : 'integer', '#text' : '2' }, 'title' : 'yet another title', 'body' : 'yet another body' }
  ericDetails = {
    'id' : { '@type' : 'integer', '#text' : '1' },
    'first-name' : 'eric',
    'admin' : { '@type' : 'boolean', '#text' : 'true' },
    'created_at' : { '@type' : 'datetime', '#text' : '2007-04-08T15:12:06-04:00' },
    'posts' : {
      'post' : [postDetails1, postDetails2]
    }
  }
  johnDetails = {
    'id' : { '@type' : 'integer', '#text' : '2' },
    'first-name' : 'john',
    'admin' : { '@type' : 'boolean', '#text' : 'false' },
    'created_at' : { '@type' : 'datetime', '#text' : '2007-04-09T15:12:06-04:00' },
    'posts' : {
      'post' : postDetails3
    }
  }
  chadDetails = {
    'id' : { '@type' : 'integer', '#text' : '3' },
    'first-name' : 'chad',
    'admin' : { '@type' : 'boolean', '#text' : 'true' },
    'created_at' : { '@type' : 'datetime', '#text' : '2007-04-10T15:12:06-04:00' }
  }

  allUsersDetails = {
    'users' : {
      'user' : [ericDetails, johnDetails, chadDetails]
    }
  }

  allPostsDetails = {
    'posts' : {
      'post' : [postDetails1, postDetails2, postDetails3]
    }
  }

  // Chad with ID 1, for testing remote site support
  remoteChad = {
    'id' : { '@type' : 'integer', '#text' : '1' },
    'first-name' : 'chad',
    'admin' : { '@type' : 'boolean', '#text' : 'true' },
    'created_at' : { '@type' : 'datetime', '#text' : '2007-04-10T15:12:06-04:00' }
  }

  // test for belongs_to, separate from normal universe
  postIncludeDetails = {
    'id' : { '@type' : 'integer', '#text' : '3' },
    'title' : 'another title',
    'body' : 'another body',
    'user' : ericDetails
  }

  // test for finding all, but where ObjTree doesn't use an array because there is only one result
  allWithOneUserDetails = {
    'users' : {
      'user' : ericDetails
    }
  }

  newUserDetails = {
    'first-name' : undefined,
    'admin' : { '@type' : 'boolean', '#text' : 'false' },
    'created_at' : { '@type' : 'datetime' }
  }
  newUserJSON = '{attributes: {first_name: null, admin: null, created_at: null}}'

  Resource.requestAndParse = function (format, callback, url, options, user_callback, remote) {
    if (remote && format == "json") {
      jesterCallback = function(json){
        result = callback(json);
        if (user_callback && typeof(user_callback) == "function") {
          changed = true;
          user_callback(result);
        }
      }
      eval('var result = ' + Internet[url]);
      return result;
    }

    var result = Internet[url];

    if (format == "json")
      eval('result = ' + result);

    if (callback)
      result = callback(result);

    if (user_callback) {
      changed = true;
      if (typeof(user_callback) == "function")
        result = user_callback(result);
      else {
        user_callback = user_callback.on200 || user_callback.onComplete;
        if (user_callback)
          result = user_callback(result);
      }
    }

    return result;
  }

  // JSON callback
  jesterCallback = null;

  // mock out the Internet
  Internet = {}

  // Remote models
  Resource.model("User", {prefix: "http://www.thoughtbot.com", format: "json"})
  Resource.model("Post", {prefix: "http://www.thoughtbot.com", format: "json"})

  Internet[User._show_url(1)] = "jesterCallback(" + ericJSON + ");";
  Internet[Post._show_url(1)] = "jesterCallback(" + postJSON + ");";
  Internet[User._list_url()] = "jesterCallback(" + usersJSON + ");";

  // JSON models
  Resource.model("User", {format: "json"})
  Resource.model("Post", {format: "json"})

  Internet[User._show_url(1)] = ericJSON;
  Internet[Post._show_url(1)] = postJSON;
  Internet[User._list_url()] = usersJSON;
  Internet[User._new_url()] = newUserJSON;

  // Path prefixed models
  Resource.model("Post", {prefix: "/users/:user_id"})

  Internet[Post._show_url({id: 1, user_id: 1})] = {'post': postDetails1};

  // XML models
  Resource.model("User")
  Resource.model("Post")

  Internet[User._show_url(1)] = {'user': ericDetails};
  Internet[User._show_url(2)] = {'user': johnDetails};
  Internet[User._show_url(3)] = {'user': chadDetails};
  Internet[Post._show_url(1)] = {'post': postDetails1};
  Internet[Post._show_url(2)] = {'post': postDetails2};
  Internet[Post._show_url(3)] = {'post': postDetails3};
  Internet[User._list_url()] = allUsersDetails;
  Internet[Post._list_url()] = allPostsDetails;
  Internet[User._show_url({id: 1, lite: true})] = {'user': {'first-name' : 'eric'}};
  Internet[User._list_url({admin: true})] = {'users': {'user': [ericDetails, chadDetails]}};
  Internet[User._list_url({admin: false})] = {'users': {'user': johnDetails}};
  Internet[User._new_url()] = {'user' : newUserDetails};


  // used to make sure callbacks get called
  changed = false;
  assert_changed = function() {assert(changed); changed = false;}

  return true;
}


/********************************************
 ************ FIND TESTS ********************
 ********************************************/

function testFind() {
  var eric = User.find(1);

  assertEquals("Attributes should be filled in correctly on find().", 'eric', eric.first_name);
  assert("Boolean attributes should be auto-transformed (when format is XML).", eric.admin);
  assertEquals("Date attributes should be auto-transformed (when format is XML).", Date, eric.created_at.constructor);
  assertEquals("Integer attributes should be auto-transformed (when format is XML).", 1, eric.id);

  assertEquals("Eric should have two posts.", 2, eric.posts.length);
  assertEquals("Eric's first post should have ID 1.", 1, eric.posts[0].id);
  assertEquals("Eric's post's title wasn't loaded.", 'the title', eric.posts[0].title);
  assertEquals("Eric's post's body wasn't loaded.", 'the body', eric.posts[0].body);
  assertEquals("Eric's second post's title wasn't loaded.", 'another title', eric.posts[1].title);
  assertEquals("Eric's second post's body wasn't loaded.", 'another body', eric.posts[1].body);

  // failed find
  var eric = User.find(50);
  assert(eric == null);

  // with params
  var ericLite = User.find(1, {lite: true});
  assertEquals("eric", ericLite.first_name);
  assertEquals(1, ericLite.id); // make sure ID gets autoset
  assertEquals(2, ericLite._properties.length);
  assert(typeof(ericLite.admin) == "undefined");

  // test function form of callback
  User.find(1, {}, function(eric) {
    assertEquals('eric', eric.first_name);
    assert_changed();
  });

  // test options form of callback
  User.find(1, {}, {on200: function(eric) {
    assertEquals('eric', eric.first_name);
    assert_changed();
  }});

  // test onComplete is preserved
  User.find(1, {}, {onComplete: function(eric) {
    assertEquals('eric', eric.first_name);
    assert_changed();
  }});

  // test that I can use options form but force it to be synchronous anyway
  var eric = User.find(1, {}, {asynchronous: false});
  assertEquals('eric', eric.first_name);

  // test that I can omit the params hash completely if I pass a straight callback
  User.find(1, function(eric) {
    assertEquals('eric', eric.first_name);
    assert_changed();
  });

  // test path prefixed find
  Resource.model("Post", {prefix: "/users/:user_id"})
  var post = Post.find(1, {user_id: 1})
  assertEquals(1, post.id)
  assertEquals("the body", post.body)

  // test JSON find
  Resource.model("User", {format: "json"})
  var eric = User.find(1);
  assertEquals("Eric wasn't found through JSON find(1).", 'eric', eric.first_name);
  assertEquals("ID didn't get transformed using JSON.", 1, eric.id);
  assertEquals("1", eric.admin.toString());

  // test Remote find
  Resource.model("User", {prefix: "http://www.thoughtbot.com", format: "json"})
  var eric;
  User.find(1, {}, function(eric) {
    assertEquals("Eric wasn't found through remote find(1).", 'eric', eric.first_name);
    assertEquals("1", eric.admin.toString());
    assert_changed();
  });
}

function testFindAll() {
  var users = User.find('all');
  assertEquals(3, users.length);

  var eric = User.find(1);
  for (var i =0; i<eric._properties.length; i++)
    assertEquals(eric._properties[i], users[0]._properties[i]);
  assertEquals(eric.first_name, users[0].first_name);
  assertEquals(eric.constructor, users[0].constructor);
  for (var i=0; i<3; i++)
    assertEquals(i+1, users[i].id)

  User.find('all', {}, function(users) {
    assertEquals(3, users.length);
    assertEquals(eric.constructor, users[0].constructor);
    assert_changed();
  });

  // test that there's no issue with one element arrays in XML
  Internet[User._list_url()] = allWithOneUserDetails;
  var users = User.find("all");
  assertEquals(1, users.length);

  // with params
  var admins = User.find("all", {admin: true})
  assertEquals(2, admins.length);
  assertEquals("eric", admins[0].first_name);
  assertEquals("chad", admins[1].first_name);

  // JSON find all
  Resource.model("User", {format: "json"})
  var users = User.find('all');
  assertEquals("Found eric as first user in JSON :all", "eric", users[0].first_name)
  assertEquals("Found john as second user in JSON :all", "john", users[1].first_name)

  // Remote find all
  var users;
  User.find('all', {}, function(users) {
    assert_changed();
    assertEquals(2, users.length);
    assertEquals("Found eric as first user in remote JSON :all", "eric", users[0].first_name)
    assertEquals("Found john as second user in remote JSON :all", "john", users[1].first_name)
  });
}

function testFindFirst() {
  var eric = User.find('first');
  var eric2 = User.find(1);

  assertEquals(eric.first_name, eric2.first_name);
  assertEquals(eric.constructor, eric2.constructor);

  User.find('first', {}, function(eric) {
    assertEquals(eric.first_name, eric2.first_name);
    assertEquals(eric.constructor, eric2.constructor);
    assert_changed();
  });

  // with params
  var admin = User.find("first", {admin: true})
  assertEquals("eric", admin.first_name);
}

// reload is a find operation
function testReload() {
  var eric = User.find(1)

  // fake that the user details got updated
  Internet[User._show_url(1)] = {'user': johnDetails}

  eric.reload();
  assertEquals("john", eric.first_name)

  // switch back
  Internet[User._show_url(2)] = {'user': ericDetails}

  eric.reload(function(ignoreMe) {
    assertEquals("eric", eric.first_name)
    assert_changed();
  });
}

/********************************************
 ************ ASSOCIATION TESTS *************
 ********************************************/

function testAssociations() {
  // has_many
  var eric = User.find(1);
  assertEquals(1, eric._associations.length);
  assertEquals("posts", eric._associations[0]);
  assertEquals(Post, eric.posts[0].klass);
  assertEquals(2, eric.posts.length);

  var john = User.find(2);
  assertEquals(1, john.posts.length);

  // auto-model associations if need be
  var original_url = Post._show_url();
  Post = undefined;
  var eric = User.find(1);
  assertEquals("posts", eric._associations[0]);
  assertEquals(Post, eric.posts[0].klass);
  assertEquals("Auto-generated Post class should have same values as the original one.", original_url, Post._show_url());
  assertEquals("post", Post._singular);
  assertEquals(User._format, Post._format);
  assertEquals(User._prefix, Post._prefix);

  // belongs_to/has_one
  Resource.model("Post");
  Internet[Post._show_url(1)] = {'post': postIncludeDetails};
  var post = Post.find(1);
  assertEquals(1, post._associations.length);
  assertEquals("user", post._associations[0]);
  assertEquals(User, post.user.klass)

  // auto-model belongs_to/has_one
  var original_url = User._show_url();
  User = undefined;
  var post = Post.find(1);
  assertEquals("user", post._associations[0]);
  assertEquals(User, post.user.klass);
  assertEquals(original_url, User._show_url());
  assertEquals("user", User._singular);
  assertEquals(Post._format, User._format);
  assertEquals(Post._prefix, User._prefix);
}


/********************************************
 ************ VALIDATION TESTS **************
 ********************************************/

function testValidCheck() {
  var user = User.build();

  assert(user.valid());
  user.errors[0] = 'error';
  assert(! user.valid());
}

function testInvalidSave() {
  var user = User.build();
  user._setProperties({ 'first_name' : '' });
  assert(user.new_record());

  Ajax = { Request : function (url, options) {
    this.transport = {
      status : 200,
      responseText : "<errors><error>First name can't be blank</error></errors>"
    };
  }}

  assert(! user.save());
  assert(! user.valid());
  assertEquals(1, user.errors.length);
  assertEquals("First name can't be blank", user.errors[0]);

  var user = User.build();
  user._setProperties({ 'id' : '1', 'first_name' : '' });
  assert(! user.new_record());

  Ajax = { Request : function (url, options) {
    this.transport = {
      status : 200,
      responseText : "<errors><error>First name can't be blank</error></errors>"
    };
  }}

  assert(! user.save());
  assert(! user.valid());
  assertEquals(1, user.errors.length);
  assertEquals("First name can't be blank", user.errors[0]);
}



/********************************************
 ************ CRUD TESTS ********************
 ********************************************/

function testDestroy() {
  var eric = User.build({id: 1})
  Ajax = { Request : function (url, options) {
      this.transport = { status : 500 };
    }
  }
  assert(!eric.destroy());
  assertEquals(1, eric.id);

  var eric = User.build({id: 1})
  Ajax = { Request : function (url, options) {
      this.transport = { status : 200 };
    }
  }
  assertEquals(eric, eric.destroy());
  assertNull(eric.id);

  var eric = User.build({id: 1})
  eric.destroy(function(result) {
    assertEquals(eric, eric.destroy());
    assertNull(eric.id);
    assert_changed();
  });

  // Path prefixed destroy
  Resource.model("Post", {prefix: "/users/:user_id"});

  var post = Post.build({id: 3, user_id: 1});
  var uriRegEx = RegExp("/users/1/posts/3");
  Ajax = { Request : function (url, options) {
      assert("URL passed ("+url+") does not match expected path: "+uriRegEx,
             uriRegEx.test(url));
      this.transport = { status: 200 };
    }
  }

  Post.destroy({id: 3, user_id: 1});

  assertEquals(post, post.destroy());
  assertNull(post.id);
}

function testSaveNewRecord() {
  var eric = User.build({ 'first_name' : 'eric' });
  assert(eric.new_record());

  Ajax = { Request : function (url, options) {
    this.transport = {
      status : 201,
      getResponseHeader : function (header) {return 'http://www.thoughtbot.com/users/1';}
    };
  }}

  assert(eric.save());
  assertEquals(1, eric.id);
  assertEquals(1, eric.attributes().id);

  var eric = User.build({ 'first_name' : 'eric' });
  eric.save(function(saved) {
    assert(saved);
    assertEquals(1, eric.id);
    assert_changed();
  });
}

function testSaveExistingRecord() {
  var eric = User.build({ 'id' : '1', 'first_name' : 'eric' });
  assert(! eric.new_record());

  Ajax = { Request : function (url, options) {
    this.transport = { status : 200 };
  }};

  assert(eric.save());

  eric.save(function(saved) {
    assert(saved);
    assert_changed();
  });
}

function testSaveReloadsUponFullResponse() {
  // new record
  var john = User.build({'first_name' : 'john' });

  Ajax = { Request : function (url, options) {
    this.transport = {
      status: 201,
      getResponseHeader : function (header) {return 'http://www.thoughtbot.com/users/1';},
      responseText : ericXML
    }
  }};

  john.save();

  assertEquals(1, john.id);
  assertEquals("eric", john.first_name)

  // existing record
  var john = User.build({'first_name' : 'john', 'id' : 2});

  Ajax = { Request : function (url, options) {
    this.transport = {
      status: 200,
      responseText : ericXML
    }
  }};

  john.save();

  assertEquals("eric", john.first_name);
  assertEquals(1, john.id); // if the server changes the ID, so be it
}

function testBuild() {
  // test without checking new.xml
  var eric = User.build();
  assertEquals(User, eric.klass);
  assertEquals(0, eric._properties.length);

  // test with checking new.xml
  Resource.model('User', {checkNew: true});
  var eric = User.build();
  assertEquals(3, eric._properties.length);

  // test with checking new.json
  Resource.model('User', {checkNew: true, format: 'json'});
  var eric = User.build();
  assertEquals(3, eric._properties.length);

  // test that specific attributes will override what new returns
  var eric = User.build({first_name: "eric", admin: true, daisies: 5});
  assertEquals(4, eric._properties.length);
  assertEquals("eric", eric.first_name);
  assertEquals(true, eric.admin);
  assertEquals(5, eric.daisies);
}

function testSetAttributes() {
  var eric = User.find(1);
  newAttrs = {
    'first_name' : 'todd',
    'admin' : false,
  }
  oldAttrs = eric.attributes();
  eric.setAttributes(newAttrs);
  assertEquals('first_name attribute not updated.', 'todd', eric.first_name);
  assertEquals('admin attribute not updated.', false, eric.admin);
  assertEquals('created_at attribute not preserved.', oldAttrs.created_at, eric.created_at);
  eric.reload();
  assertEquals('old name attribute not restored.', oldAttrs.first_name, eric.first_name);
}

function testUpdateAttributes() {
  var eric = User.find(1);
  newAttrs = {
    'first_name' : 'todd',
    'admin' : false,
  }
  oldAttrs = eric.attributes();
  eric.updateAttributes(newAttrs);
  assertEquals('first_name attribute not updated.', 'todd', eric.first_name);
  assertEquals('admin attribute not updated.', false, eric.admin);
  assertEquals('created_at attribute not preserved.', oldAttrs.created_at, eric.created_at);
  eric.reload();
  assertEquals('new name attribute not preserved.', oldAttrs.first_name, eric.first_name);
}

/********************************************
 ************* PARSING TESTS ****************
 ********************************************/

function testAttributesFromJSON() {
  eval('var json = ' + ericJSON);
  var attributes = User._attributesFromJSON(json);
  assertEquals("The ID should be auto-transformed from JSON.", 1, attributes.id);
  assertEquals("1", attributes.admin.toString()); // toString() since "1" == true
  assertEquals("eric", attributes.first_name);
  assertEquals(Date, attributes.created_at.constructor)

  // test that the surrounding attributes hash is optional
  var attributes = User._attributesFromJSON({email: "emill@thoughtbot.com", id: "1"});
  assertEquals("emill@thoughtbot.com", attributes.email);
  assertEquals(1, attributes.id);

  assertEquals(false, User._attributesFromJSON({}));
  assertEquals(false, User._attributesFromJSON([]));
  assertEquals(false, User._attributesFromJSON(null));
}

function testAttributesFromTree() {
  var attributes = User._attributesFromTree(ericDetails);
  assertEquals("Integers auto-transformed from XML.", 1, attributes.id);
  assert(attributes.admin);
  assertEquals("eric", attributes.first_name);
}

function testErrorsFromJSON() {
  var errorJSON = '[["name", "can\'t be blank"], ["city", "can\'t be blank"]]';
  Resource.model('User', {format: 'json'});
  var eric = User.build();
  var errors = eric._errorsFrom(errorJSON);

  assertEquals(2, errors.length);
  assertEquals("Name can't be blank", errors[0]);
  assertEquals("City can't be blank", errors[1]);

  assertEquals(false, eric._errorsFrom(' '));
  assertEquals(false, eric._errorsFrom(''));
  assertEquals(false, eric._errorsFrom(null));
}

function testErrorsFromXML() {
  var errorXML = "<errors><error>Name can't be blank</error><error>City can't be blank</error></errors>"
  var eric = new User();
  var errors = eric._errorsFromXML(errorXML);

  assertEquals(2, errors.length);
  assertEquals("Name can't be blank", errors[0]);
  assertEquals("City can't be blank", errors[1]);

  assertEquals(false, eric._errorsFromXML(' '));
  assertEquals(false, eric._errorsFromXML(''));
  assertEquals(false, eric._errorsFromXML(null));
}

/********************************************
 ************ INTERNAL TESTS ****************
 ********************************************/

 function testGlobalScope() {
  // IE7 compatibility
  assertEquals("IE7 incompatibility", User, window.User);
 }

function testSetProperties() {
  var eric = User.build()
  assertUndefined(eric.first_name);

  eric._setProperties({ 'first_name' : 'eric' });
  assertEquals('eric', eric.first_name);
  assertEquals(1, eric._properties.length);
  assertEquals('first_name', eric._properties[0]);
}

function testSetAssociations() {
  var eric = User.build()
  assertUndefined(eric.posts);
  var posts = [Post.find(1)]

  eric._setAssociations({ 'posts' : posts });
  assertEquals(posts, eric.posts);
  assertEquals(1, eric._associations.length);
  assertEquals('posts', eric._associations[0]);
}

function testNameAccessors() {
  assertEquals('User', User._name);
  assertEquals('user', User._singular);
  assertEquals('users', User._plural);
  assertEquals(default_prefix(), User._prefix);

  Resource.model("OrderLineItem");
  assertEquals('OrderLineItem', OrderLineItem._name);
  assertEquals('order_line_item', OrderLineItem._singular);
  assertEquals('order_line_items', OrderLineItem._plural);
  assertEquals('order-line-item', OrderLineItem._singular_xml);
  assertEquals('order-line-items', OrderLineItem._plural_xml);
  assertEquals(default_prefix(), OrderLineItem._prefix);
}

function testPrefix() {
  Resource.model('User', {prefix: 'http://www.thoughtbot.com'});
  assertEquals("Absolute prefix.", 'http://www.thoughtbot.com', User._prefix);

  Resource.model('User', {prefix: 'public/forum'});
  assertEquals("Path prefix with no slash.", default_prefix() + '/public/forum', User._prefix);

  Resource.model('User', {prefix: '/public/forum'});
  assertEquals( "Path prefix with slash.", default_prefix() + '/public/forum', User._prefix);
}

function testPluralization() {
  // base case
  Resource.model('Person');
  assertEquals('person', Person._singular);
  assertEquals('people', Person._plural);

  // singular override
  Resource.model('Person', {singular: 'user'})
  assertEquals('user', Person._singular);
  assertEquals('users', Person._plural);

  // plural override
  Resource.model('Person', {plural: 'persons'});
  assertEquals('person', Person._singular);
  assertEquals('persons', Person._plural);
}

function testLocationUrls() {
  Resource.model('User', {prefix: 'http://www.thoughtbot.com'});
  var eric = User.build({id: 1})

  assertEquals('http://www.thoughtbot.com/users/1.xml', eric._show_url());
  assertEquals('http://www.thoughtbot.com/users/2.xml', User._show_url(2));
  assertEquals('http://www.thoughtbot.com/users/2.xml', User._show_url({id: 2}));
  assertEquals('http://www.thoughtbot.com/users/1.xml?lite=true', User._show_url({id: 1, lite: true}))
  assertEquals('http://www.thoughtbot.com/users/1.xml?lite=true', eric._show_url({lite: true}))

  assertEquals('http://www.thoughtbot.com/users.xml', User._list_url());
  assertEquals('http://www.thoughtbot.com/users.xml', User._list_url({}));
  assertEquals('http://www.thoughtbot.com/users.xml?admin=true&name=eric&lasers=5', User._list_url({admin: true, name: "eric", lasers: 5}));

  assertEquals('http://www.thoughtbot.com/users/new.xml', User._new_url());
  assertEquals('http://www.thoughtbot.com/users/new.xml?lite=true', User._new_url({lite: true}));

  Resource.model('Post', {prefix: 'http://www.thoughtbot.com/users/:user_id'});
  var post = Post.build({id: 1})

  assertEquals('http://www.thoughtbot.com/users/1/posts/1.xml', post._show_url({user_id: 1}));
  assertEquals('http://www.thoughtbot.com/users/1/posts/2.xml', Post._show_url({id: 2, user_id: 1}));
  assertEquals('http://www.thoughtbot.com/users/2/posts.xml', Post._list_url({user_id: 2}));
  assertEquals('http://www.thoughtbot.com/users/1/posts.xml?lite=true', Post._list_url({user_id: 1, lite: true}));
  assertEquals('http://www.thoughtbot.com/users/:user_id/posts.xml', Post._list_url());
  assertEquals('http://www.thoughtbot.com/users/:user_id/posts.xml', Post._list_url({}));
}

function testNewRecordCheck() {
  var eric = User.build();
  assert(eric.new_record());

  eric.id = 1;
  assert(! eric.new_record());
}

function testSetErrors() {
  var errors = ["Name can't be blank", "City can't be blank"]
  Resource.model('User');
  var eric = new User();
  assert(eric.valid());

  eric._setErrors(errors);
  assert(!eric.valid());
  assertEquals(2, eric.errors.length);
  assertEquals("Name can't be blank", eric.errors[0]);
  assertEquals("City can't be blank", eric.errors[1]);
}

    </script>
  </body>
</html>
